﻿using System;
using System.ComponentModel;
using System.Reflection;

namespace IMDBApi.App
{
    /// <summary>
    /// ...
    /// </summary>
    public class GenericBackgroundWorker
    {
        #region Constructors

        public GenericBackgroundWorker() { }

        #endregion

        #region Properties

        BackgroundWorker backgroundWorker = new BackgroundWorker();

        public delegate K WorkerDoWork<S, T, K>(S backgroundWorker, T inputArgument, DoWorkEventArgs e);
        public delegate void WorkerProgressChangedDelegate<T>(T inputArgument, ProgressChangedEventArgs e);
        public delegate void WorkerCompletedDelegate<K>(K outputArgument);
        public delegate void WorkerCompletedWithError<K>(Exception exception);
        public delegate void WorkerCanceled<K>();

        #endregion

        #region Methods

        /// <summary>
        /// Use this method to determine if the latest worker has a cancellation pending
        /// </summary>
        public bool CancellationPending()
        {
            return this.backgroundWorker.CancellationPending;
        }

        /// <summary>
        /// Use this method to determine if the latest worker is busy
        /// </summary>
        /// <typeparam name="T">is the type of the input argument</typeparam>
        /// <param name="percentProgress"></param>
        /// <param name="userState"></param>
        public void ReportProgress<T>(int percentProgress)
        {
            this.backgroundWorker.ReportProgress(percentProgress);
        }

        /// <summary>
        /// Use this method to determine if the latest worker is busy
        /// </summary>
        /// <typeparam name="T">is the type of the input argument</typeparam>
        /// <param name="percentProgress"></param>
        /// <param name="userState"></param>
        public void ReportProgress<T>(int percentProgress, T userState)
        {
            this.backgroundWorker.ReportProgress(percentProgress, userState);
        }

        /// <summary>
        /// Use this method to determine if the latest worker is busy
        /// </summary>
        public bool IsBusy()
        {
            return this.backgroundWorker.IsBusy;
        }

        /// <summary>
        /// Use this method to cancel the lastest worker
        /// </summary>
        public void CancelAsync()
        {
            this.backgroundWorker.CancelAsync();
        }

        /// <summary>
        /// Use this method if you need to handle when the worker is completed
        /// </summary>
        /// <typeparam name="T">is the type of the input parameter</typeparam>
        /// <typeparam name="K">is the type of the output result</typeparam>
        /// <param name="inputArgument"></param>
        /// <param name="workerDoWork"></param>
        /// <param name="workerCompleted"></param>
        /// <param name="workerCompletedWithError"></param>
        /// <param name="workerCanceled"></param>
        public void RunAsync<S, T, K>(WorkerDoWork<S, T, K> workerDoWork, T inputArgument, WorkerProgressChangedDelegate<T> workerProgressChanged, WorkerCompletedDelegate<K> workerCompleted, WorkerCompletedWithError<K> workerCompletedWithError, WorkerCanceled<K> workerCanceled)
        {
            BackgroundWorker bw = GetBackgroundWorker<S, T, K>(workerDoWork, workerProgressChanged, workerCompleted, workerCompletedWithError, workerCanceled);

            this.backgroundWorker = bw;

            this.backgroundWorker.RunWorkerAsync(inputArgument);
        }

        /// <summary>
        /// Use this method if you don't need to handle when the worker is canceled
        /// </summary>
        /// <param name="workerDoWork"></param>
        /// <param name="inputArgument"></param>
        /// <param name="workerCompleted"></param>
        /// <param name="workerCompletedWithError"></param>
        public void RunAsync<S, T, K>(WorkerDoWork<S, T, K> workerDoWork, T inputArgument, WorkerCompletedDelegate<K> workerCompleted, WorkerCompletedWithError<K> workerCompletedWithError)
        {
            RunAsync(workerDoWork, inputArgument, null, workerCompleted, workerCompletedWithError, null);
        }

        /// <summary>
        /// Use this method if you don't need to handle when the worker is completed
        /// </summary>
        /// <param name="doWork"></param>
        /// <param name="inputArgument"></param>
        public void RunAsync<S, T, K>(WorkerDoWork<S, T, K> doWork, T inputArgument)
        {
            RunAsync(doWork, inputArgument, null, null, null, null);
        }

        private BackgroundWorker GetBackgroundWorker<S, T, K>(WorkerDoWork<S, T, K> workerDoWork, WorkerProgressChangedDelegate<T> workerProgressChanged, WorkerCompletedDelegate<K> workerCompleted, WorkerCompletedWithError<K> workerCompletedWithError, WorkerCanceled<K> workerCanceled)
        {
            BackgroundWorker bw = new BackgroundWorker();

            if( workerProgressChanged != null )
                bw.WorkerReportsProgress = true;
            if( workerCanceled != null )
                bw.WorkerSupportsCancellation = true;

            bw.DoWork += (sender, args) =>
            {
                if (workerDoWork != null)
                {
                    args.Result = (K)workerDoWork((S)sender, (T)args.Argument, args);
                }
            };

            bw.ProgressChanged += (sender, args) =>
            {
                if (workerProgressChanged != null)
                    workerProgressChanged((T)args.UserState, args);
            };

            bw.RunWorkerCompleted += (sender, args) =>
            {
                if (workerCompleted != null)
                {
                    try
                    {
                        workerCompleted((K)args.Result);
                    }
                    catch (InvalidOperationException)
                    {
                        if (workerCanceled != null)
                            workerCanceled();
                    }
                    catch (TargetInvocationException ex)
                    {
                        if (workerCompletedWithError != null)
                            workerCompletedWithError(ex.InnerException);
                    }
                }
            };
            return bw;
        }

        #endregion
    }
}